/*
 * <copyright>
 *  Copyright 2011 BBN Technologies
 * </copyright>
 */

package com.bbn.openmap.dataAccess.mapTile;

import java.awt.Color;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.bbn.openmap.omGraphics.DrawingAttributes;
import com.bbn.openmap.util.FileUtils;

/**
 * Creates a KML grid file for import into Google Earth. This file shows the
 * tile boundaries for a specified tile zoom level.
 * 
 * @author ddietrick
 */
public class KMLTileGridMaker {
   protected String docTitle;
   protected int level;
   protected List<Tile> tiles;
   protected DrawingAttributes drawingAttributes = DrawingAttributes.getDefaultClone();

   /**
    * @param builder
    */
   protected KMLTileGridMaker(Builder builder) {
      this.level = builder.zoomLevel;
      tiles = makeTiles(level);
      this.docTitle = builder.docTitle;

      drawingAttributes.setLinePaint(builder.lineColor);
      drawingAttributes.setFillPaint(builder.fillColor);
      drawingAttributes.setSelectPaint(builder.labelColor);
   }

   protected List<Tile> makeTiles(int level) {
      List<Tile> ret = new ArrayList<Tile>();
      ZoomLevelInfo zli = new ZoomLevelInfo();
      zli.setZoomLevel(level);
      int edgeTileCount = zli.getEdgeTileCount();
      OSMMapTileCoordinateTransform transform = new OSMMapTileCoordinateTransform();
      zli.setScale(transform.getScaleForZoom(level));
      for (int x = 0; x < edgeTileCount; x++) {
         for (int y = 0; y < edgeTileCount; y++) {
            Point2D ulllp = transform.tileUVToLatLon(new Point2D.Double(x, y), level);
            Point2D lrllp = transform.tileUVToLatLon(new Point2D.Double(x + 1, y + 1), level);

            ret.add(new Tile(x, y, ulllp, lrllp));
         }
      }
      return ret;
   }

   protected void writeDoc(OutputStream output)
         throws ParserConfigurationException, TransformerException {

      DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder documentBuilder;
      documentBuilder = documentBuilderFactory.newDocumentBuilder();
      Document document = documentBuilder.newDocument();

      Element kmlElement = appendChild(document, document, createKMLElement(document));
      Element docElement = appendChild(document, kmlElement, "Document", null);
      appendChild(document, docElement, createStyle(document));
      appendChild(document, docElement, createTiles(document));

      TransformerFactory transformerFactory = TransformerFactory.newInstance();
      Transformer transformer = transformerFactory.newTransformer();
      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
      DOMSource source = new DOMSource(document);

      StreamResult result = new StreamResult(output);
      transformer.transform(source, result);
   }

   protected Element createKMLElement(Document doc) {
      Element rootElement = doc.createElement("kml");
      rootElement.setAttribute("xmlns", "http://www.opengis.net/kml/2.2");
      rootElement.setAttribute("xmlns:gx", "http://www.google.com/kml/ext/2.2");
      rootElement.setAttribute("xmlns:kml", "http://www.opengis.net/kml/2.2");
      rootElement.setAttribute("xmlns:atom", "http://www.w3.org/2005/Atom");
      return rootElement;
   }

   protected Element createTiles(Document doc) {
      Element folderElement = doc.createElement("Folder");
      appendChild(doc, folderElement, "name", "Tile Layout for Zoom Level " + level);
      appendChild(doc, folderElement, "open", "1");

      List<Tile> tiles = makeTiles(level);

      for (Tile tile : tiles) {
         Point2D ul = tile.ul;
         Point2D lr = tile.lr;

         StringBuilder buf = new StringBuilder();

         buf.append(ul.getX() + "," + ul.getY() + ",0 ");
         buf.append(lr.getX() + "," + ul.getY() + ",0 ");
         buf.append(lr.getX() + "," + lr.getY() + ",0 ");
         buf.append(ul.getX() + "," + lr.getY() + ",0 ");
         buf.append(ul.getX() + "," + ul.getY() + ",0 ");

         // Polygon representing tile
         Element placemark = appendChild(doc, folderElement, "Placemark", null);

         appendChild(doc, placemark, "name", tile.title);
         appendChild(doc, placemark, "styleUrl", "#" + styleString);

         Element polygon = appendChild(doc, placemark, "Polygon", null);
         appendChild(doc, polygon, "tessellate", "1");

         Element obiElement = appendChild(doc, polygon, "outerBoundaryIs", null);
         Element lrElement = appendChild(doc, obiElement, "LinearRing", null);
         /*Element coordElement = */appendChild(doc, lrElement, "coordinates", buf.toString());

         // Label for tile
         Element placemark2 = appendChild(doc, folderElement, "Placemark", null);
         appendChild(doc, placemark2, "name", tile.title);
         appendChild(doc, placemark2, "styleUrl", "#" + styleString);
         Element point = appendChild(doc, placemark2, "Point", null);
         appendChild(doc, point, "coordinates", ((lr.getX() + ul.getX()) / 2) + "," + ((ul.getY() + lr.getY()) / 2) + ",0");
      }

      return folderElement;
   }

   String styleString = "tileborderstyle";

   protected Element createStyle(Document doc) {
      Element style = doc.createElement("Style");
      style.setAttribute("id", styleString);
      Element label = appendChild(doc, style, "LabelStyle", null);
      // Element icon = appendChild(doc, style, "IconStyle", null);
      Element line = appendChild(doc, style, "LineStyle", null);
      Element poly = appendChild(doc, style, "PolyStyle", null);

      String labelColorString = Integer.toHexString(((Color) drawingAttributes.getSelectPaint()).getRGB());
      String lineColorString = Integer.toHexString(((Color) drawingAttributes.getLinePaint()).getRGB());
      String fillColorString = Integer.toHexString(((Color) drawingAttributes.getFillPaint()).getRGB());

      appendChild(doc, label, "color", labelColorString);
      appendChild(doc, line, "color", lineColorString);
      appendChild(doc, poly, "color", fillColorString);

      return style;
   }

   protected Element appendChild(Document doc, Node parent, String childField, String value) {
      Element element = (Element) parent.appendChild(doc.createElement(childField));
      if (value != null) {
         element.setTextContent(value);
      }
      return element;
   }

   protected Element appendChild(Document doc, Node parent, Element child) {
      parent.appendChild(child);
      return child;
   }

   /**
    * @param args
    */
   public static void main(String[] args) {
      Color fillColor = new Color(0x3300FF00, true);
      Color lineColor = new Color(0xFF00FF00);

      com.bbn.openmap.util.ArgParser ap = new com.bbn.openmap.util.ArgParser("KMLTileGridMaker");

      ap.add("level", "Tile Zoom Level to create grid for.", 1);

      if (!ap.parse(args)) {
         ap.printUsage();
         System.exit(0);
      }

      String[] arg = ap.getArgValues("level");
      if (arg != null) {

         try {
            int level = Integer.parseInt(arg[0]);
            new Builder(level).setLineColor(lineColor).setFillColor(fillColor).setLabelColor(lineColor).go();
         } catch (ParserConfigurationException e) {
            e.printStackTrace();
         } catch (TransformerException e) {
            e.printStackTrace();
         } catch (IOException e) {
            e.printStackTrace();
         } catch (NumberFormatException nfe) {
            nfe.printStackTrace();
         }
      }
   }

   protected class Tile {
      protected int uvx;
      protected int uvy;
      protected Point2D ul;
      protected Point2D lr;
      protected String title;

      protected Tile(int uvx, int uvy, Point2D ul, Point2D lr) {
         this.uvx = uvx;
         this.uvy = uvy;
         this.ul = ul;
         this.lr = lr;

         title = uvx + "_" + uvy;
      }
   }

   /**
    * Use this class to run the KMLTileGridMake. Create a builder, add any
    * customization you want, call go() when you want to create the file.
    * 
    * @author ddietrick
    */
   public static class Builder {
      int zoomLevel;
      Color lineColor = Color.WHITE;
      Color fillColor = Color.WHITE;
      Color labelColor = Color.WHITE;
      String docTitle;
      String fileName;
      OutputStream output;
      boolean closeOutput = false;

      public Builder() {
         this(5);
      }

      public Builder(int tileZoomLevel) {
         zoomLevel = tileZoomLevel;
         docTitle = "Tile Boundaries for Zoom Level " + zoomLevel;
      }

      public Builder setLineColor(Color lineColor) {
         this.lineColor = lineColor;
         return this;
      }

      public Builder setFillColor(Color fillColor) {
         this.fillColor = fillColor;
         return this;
      }

      public Builder setLabelColor(Color labelColor) {
         this.labelColor = labelColor;
         return this;
      }

      public Builder setDocTitle(String docTitle) {
         this.docTitle = docTitle;
         return this;
      }

      public Builder setFileName(String fileName)
            throws FileNotFoundException {
         this.fileName = fileName;

         File file = null;
         if (fileName != null) {
            file = new File(fileName);
         }

         if (file == null) {
            String filePath = FileUtils.getFilePathToSaveFromUser("Choose location and name of KML file");
            if (filePath != null) {
               file = new File(filePath);
            }
         }

         if (file != null) {
            output = new FileOutputStream(file);
            closeOutput = true;
         }

         return this;
      }

      /**
       * If you provide an OutputStream, you're responsible for closing it.
       * 
       * @param out
       * @return this Builder
       */
      public Builder setOutputStream(OutputStream out) {
         this.output = out;
         return this;
      }

      public void go()
            throws ParserConfigurationException, TransformerException, IOException {
         KMLTileGridMaker kml = new KMLTileGridMaker(Builder.this);

         if (output == null) {
            setFileName(null);
         }

         if (output != null) {
            kml.writeDoc(output);
            if (closeOutput) {
               output.close();
            }
         } else {
            throw new IOException("No output specified");
         }
      }
   }
}
